#include "../utils.hpp"

float vertices[] = {
    // positions          // normals           // texture coords
    -0.5f, -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 0.0f, 0.5f,  -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 0.0f,
    0.5f,  0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 1.0f, 0.5f,  0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 1.0f,
    -0.5f, 0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 0.0f,

    -0.5f, -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f, 0.5f,  -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,
    0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f, 0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,
    -0.5f, 0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f, -0.5f, -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,

    -0.5f, 0.5f,  0.5f,  -1.0f, 0.0f,  0.0f,  1.0f, 0.0f, -0.5f, 0.5f,  -0.5f, -1.0f, 0.0f,  0.0f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f, 0.5f,  -1.0f, 0.0f,  0.0f,  0.0f, 0.0f, -0.5f, 0.5f,  0.5f,  -1.0f, 0.0f,  0.0f,  1.0f, 0.0f,

    0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f, 0.5f,  0.5f,  -0.5f, 1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
    0.5f,  -0.5f, -0.5f, 1.0f,  0.0f,  0.0f,  0.0f, 1.0f, 0.5f,  -0.5f, -0.5f, 1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
    0.5f,  -0.5f, 0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f, 0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  0.0f, 1.0f, 0.5f,  -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  1.0f, 1.0f,
    0.5f,  -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  1.0f, 0.0f, 0.5f,  -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  1.0f, 0.0f,
    -0.5f, -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  0.0f, 1.0f,

    -0.5f, 0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  0.0f, 1.0f, 0.5f,  0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
    0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f, 0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
    -0.5f, 0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f, -0.5f, 0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  0.0f, 1.0f};

// 当前路径
#define SOURCE_PATH ROOT_SOURCE_PATH "/src/core/skybox/"

std::shared_ptr<Shader> instanceShader, skyboxShader;
unsigned int basicVAO, basicVBO, skyboxVAO, skyboxVBO, skyboxTex;

void init()
{
    instanceShader = std::make_shared<Shader>(SOURCE_PATH "test.vert", SOURCE_PATH "test.frag", nullptr);
    skyboxShader = std::make_shared<Shader>(SOURCE_PATH "skybox.vert", SOURCE_PATH "skybox.frag", nullptr);

    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    // basic shader
    {
        glGenVertexArrays(1, &basicVAO);
        glGenBuffers(1, &basicVBO);

        glBindVertexArray(basicVAO);
        glBindBuffer(GL_ARRAY_BUFFER, basicVBO);

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
        glEnableVertexAttribArray(0);

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
        glEnableVertexAttribArray(1);

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
        glEnableVertexAttribArray(2);
    }

    // skybox shader
    {
        glGenVertexArrays(1, &skyboxVAO);
        glGenBuffers(1, &skyboxVBO);

        glBindVertexArray(skyboxVAO);
        glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
        glEnableVertexAttribArray(0);
    }

    // 载入光照信息
    instanceShader->enable();
    instanceShader->set_value("projection", camera->projection());

    skyboxShader->enable();
    skyboxShader->set_value("projection", camera->projection());

    // 载入天空盒纹理
    // #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
    // #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
    // #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
    // #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
    // #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
    // #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
    std::vector<const char *> faces{
        SOURCE_PATH "skybox/right.jpg",  SOURCE_PATH "skybox/left.jpg",  SOURCE_PATH "skybox/top.jpg",
        SOURCE_PATH "skybox/bottom.jpg", SOURCE_PATH "skybox/front.jpg", SOURCE_PATH "skybox/back.jpg",
    };
    glGenTextures(1, &skyboxTex);
    glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTex);
    loadCubeTexture(faces);
}

void render()
{
    // 刷新视图矩阵
    instanceShader->enable();
    instanceShader->set_value("model", Mat4x4<float>::identity());
    instanceShader->set_value("fix", Mat4x4<float>::identity());
    instanceShader->set_value("view", camera->view());
    instanceShader->set_value("viewPos", camera->position());
    instanceShader->set_value("skyboxTex", 0);

    skyboxShader->enable();

    // 移除位移部分，确保天空盒的位置向量不变
    auto view = camera->view();
    for (int i = 0; i < 3; i++)
        view(i, 3) = 0;
    skyboxShader->set_value("view", view);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 绘制其它物体
    instanceShader->enable();
    glBindVertexArray(basicVAO);
    glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTex);

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // 修改深度测试函数，让深度等于 1 的背景可以绘制
    glDepthFunc(GL_LEQUAL);

    // 绑定天空盒顶点和纹理
    skyboxShader->enable();
    glBindVertexArray(skyboxVAO);
    glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTex);

    // 绘制立方体贴图
    glDrawArrays(GL_TRIANGLES, 0, 36);

    // 恢复深度函数
    glDepthFunc(GL_LESS);
}

int main()
{
    GLFWwindow *window = initProgram();
    if (window == nullptr)
        return -1;

    // 创建顶点数据
    init();

    // 开启消息循环
    while (!glfwWindowShouldClose(window))
    {
        // 渲染指令
        render();

        // 交换缓冲，处理并调用事件
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 销毁资源
    glfwTerminate();

    return 0;
}